<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive } from "vue";
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import * as Three from 'three'
let scene = null,camera=null,renderer=null,mesh=null,element =null,maps = new Map();
// 机柜集合
const cabinets = [];
const state = reactive({
  planePos: {
    left: 0,
    top:0
  },
  planeDisplay: 'none',
});
// 选中的机柜
const curCabinet = ref({
  //名称
  name:'Loading……',
  //温度
  temperature: 0,
  //容量
  capacity: 0,
  //服务器数量
  count:0
});
const init = () => {
  crtTexture("cabinet-hover.jpg");
  // 初始化场景
  createSence();
  // 加载模型文件
  loadGLTF();
  // 初始化光源
  createLight();
  // 初始化相机
  createCamera();
  // 初始化渲染器
  createRender();
  // 初始化控制器
  createControls();
  // 开始渲染
  render();
};
// 创建场景
const createSence = () => {
  scene = new Three.Scene()
};
// 创建纹理
const crtTexture = (imgName) => {
  let curTexture = maps.get(imgName)
  console.log(imgName)
  if (!curTexture) {
    // 有机柜，电箱，门，厢房栓，空调，地板等模型替换纹理，换成我们想展示的jpg蒙皮
    curTexture=new Three.TextureLoader().load('src/assets/models/'+imgName)
    curTexture.flipY = false
    curTexture.wrapS = 1000
    curTexture.wrapT = 1000
    maps.set(
      imgName,
      curTexture
    )
  }
  return curTexture
}
// 修改模型材料材质 原始的材质是模型自带的光材质，我们需要自己的高亮效果需要替换掉模型自带的材质
const changeMat = (obj, map, color) => {
  if (map) {
    // 转成MeshBasicMaterial材质
    obj.material = new Three.MeshBasicMaterial({
      map: crtTexture(map.name)
    })
  } else {
    obj.material = new Three.MeshBasicMaterial({color})
  }
};
// 加载机柜详情
const loadCabinetDetail = async (cabinet) => {
  setTimeout(async () => {
    if (curCabinet.value) {
      curCabinet.value.temperature = 40
      curCabinet.value.count = 100
      curCabinet.value.capacity = 1000
    }
  }, 1000)
};
// 鼠标移动到机柜上触发动作
const onMouseOverCabinet = (cabinet) => {
  // console.log(cabinet);
  state.planeDisplay = 'block'
  loadCabinetDetail();
}
// 鼠标在机柜上移动时动作
const onMouseMoveCabinet = (x,y) => {
  // console.log(x,y);
  state.planePos.left = x + 'px'
  state.planePos.top = y + 'px'
};
// 鼠标移出机柜
const onMouseOutCabinet = () => {
  state.planeDisplay = 'none'
};
const selectCabinet = (x, y) => {
  const { width, height } = renderer.domElement
  //射线投射器，可基于鼠标点和相机，在世界坐标系内建立一条射线，用于选中模型
  const raycaster = new Three.Raycaster()
  //鼠标在裁剪空间中的点位
  const pointer = new Three.Vector2()

  // 鼠标的canvas坐标转裁剪坐标
  pointer.set(
    (x / width) * 2 - 1,
    -(y / height) * 2 + 1,
  )
  // 基于鼠标点的裁剪坐标位和相机设置射线投射器
  raycaster.setFromCamera(
    pointer, camera
  )
  // 选择机柜
  const intersect = raycaster.intersectObjects(cabinets)[0]
  let intersectObj = intersect ? intersect.object : null
  // 若之前已有机柜被选择，且不等于当前所选择的机柜，取消之前选择的机柜的高亮
  if (curCabinet.value && curCabinet.value !== intersectObj) {
    const material = curCabinet.value.material
    if (material) {
      material.setValues({
        map: maps.get('cabinet.jpg')
      })
    }
  }
  /* 
    若当前所选对象不为空：
      触发鼠标在机柜上移动的事件。
      若当前所选对象不等于上一次所选对象：
        更新curCabinet。
        将模型高亮。
        触发鼠标划入机柜事件。
    否则若上一次所选对象存在：
      置空curCabinet。
      触发鼠标划出机柜事件。
  */
  if (intersectObj) {
    onMouseMoveCabinet(x, y)
    if (intersectObj !== curCabinet.value) {
      curCabinet.value = intersectObj
      const material = intersectObj.material
      material.setValues({
        map: maps.get('cabinet-hover.jpg')
      })
      onMouseOverCabinet(intersectObj)
    }
  } else if (curCabinet.value) {
    curCabinet.value = null
    onMouseOutCabinet()
  }
};

// 加载模型
const loadGLTF = () => {
  const loader = new GLTFLoader();
  loader.load(
    'src/assets/models/machineRoom.gltf',
    function (gltf) {
      console.log(gltf);
      const { scene: { children } } =  gltf;
      children.forEach((obj) => {
        const { map, color} = obj.material 
        changeMat(obj, map, color);
        if (obj.name.includes('cabinet')) {
          cabinets.push(obj);
        }
      })
      scene.add(...children);
    },
    function (xhr) {
      console.log((xhr.loaded / xhr.total) * 100 + '% loaded');
    },
    function (error) {
      console.log(error);
    }
  )
};
// 创建光源
const createLight = () => {
  const light = new Three.AmbientLight(0x404040, 0.1); // 环境光
  scene.add(light);
  // 点光源
  const spotLight = new Three.SpotLight(0xffffff);
  spotLight.position.set(150, 150, 150);
  spotLight.castShadow = true;
  scene.add(spotLight);
};
// 创建相机
const createCamera = () => {
  const width = element.clientWidth;
  const height = element.clientHeight;
  const k = width / height // 窗口宽高比
  camera = new Three.PerspectiveCamera(45, k, 1, 1000);
  // 设置相机位置
  camera.position.set(0, 10, 15);
  camera.lookAt(0, 0, 0); // 设置相机方向
  scene.add(camera);
};
// 创建渲染器
const createRender = () => {
  renderer = new Three.WebGLRenderer({ antialias: true, alpha: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.setClearColor(0x3f3f3f, 1);
  element.appendChild(renderer.domElement);
  renderer.setSize(element.clientWidth, element.clientHeight);
}
// 创建控制器
const createControls = () => {
  const controls = new OrbitControls(camera, renderer.domElement);
  controls.enableDamping = true;
};
const render = () => {
  renderer.render(scene, camera);
  requestAnimationFrame(render);
};
// 监听鼠标在模型上移动
const mouseMove = ({ clientX,clientY }) => {
  selectCabinet(clientX, clientY);
}
onMounted(() => {
  element = document.getElementById('container');
  init();
})
onUnmounted(() => {
  scene = null;
  camera=null;
  renderer=null;
  mesh=null;
  element =null;
  maps = new Map();
  cabinets.length = 0;
  curCabinet.value = {
    //名称
    name:'Loading……',
    //温度
    temperature: 0,
    //容量
    capacity: 0,
    //服务器数量
    count:0
  };
});

</script>
<style scoped>
#container{
  width: 100vw;
  height: 100vh;
}
#plane{
  position: absolute;
  top: 0;
  left: 0;
  background-color: rgba(0,0,0,0.5);
  color: #fff;
  padding: 0 18px;
  transform: translate(12px,-100%);
  display: none;
}
</style>

<template>
  <div id="container" @mousemove="mouseMove">
    <div
      id='plane'
      :style="{left: state.planePos.left,top:state.planePos.top,display: state.planeDisplay}"
    >
      <p>机柜名称：{{ curCabinet?.name }}</p>
      <p>机柜温度：{{ curCabinet?.temperature }}°</p>
      <p>使用情况：{{ curCabinet?.count}} / {{ curCabinet?.capacity }}</p>
    </div>
  </div>
</template>
